-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JIT: enhance escape analysis to understand Span<T>
capture
#112543
base: main
Are you sure you want to change the base?
Conversation
`Span<T>` captured arrays can be passed to callees without escaping. Implement a very simplistic "field sensitive" analysis for `Span<T>` where we pretend the span is simply its byref field.
FYI @EgorBo The class layout rewriting isn't happening just yet, need to look at that more closely. We are OK even w/o rewriting since the backing field is a byref. Also I should perhaps restrict this more tightly to spans and make recognition of the span pointer fields simpler. Some examples where we can stack allocate: int SpanCapture()
{
Span<int> span = new int[128];
span[10] = 100;
return Use(span);
}
int SpanCapture2(int[]? x)
{
Span<int> span = x ?? new int[128];
span[10] = 100;
return Use(span);
} and one where we can't: Span<int> SpanEscape()
{
int[] x = new int[128];
Span<int> span = x;
span[10] = 100;
Use(span);
return span;
} This still has existing limitations (array must be known-size, small, and not allocated in a loop). But this along with #112168 gives us the ability to generally replace span captured stackallocs with safe constructs that can flexibly allocate on stack or heap depending on policy. The analysis does not directly generalize to other struct cases -- non-ref structs can be on the heap, and ref struct may allow their gc ref fields to be extracted and methods that mutate those fields will not be using checked barriers. But other byref-likes that don't have any object ref fields should be optimizable in the same way (if there are any such, aside from Some interesting diffs (not a lot, but some).... |
@jakobbotsch pointed out that a callee like Added checking for this but it seems awfully convoluted.
Handles cases like these: Span<int> I(Span<int> x) => x
int SpanCapture3(int[]? x)
{
Span<int> span = x ?? new int[128];
span[10] = 100;
return Use(I(span));
}
int SpanCapture4()
{
Span<int> span = new int[128];
span[10] = 100;
Span<int> x = I(span);
return Use(x);
}
Span<int> SpanEscape2()
{
int[] x = new int[128];
Span<int> span = x;
span[10] = 100;
return I(span);
} Something similar is needed for ref/out params.... NYI. |
Seems to be some issues related to |
Debugged into one failure. There is a span-captured box, via runtime/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs Lines 121 to 129 in 10532bf
and since the span does not escape, and the box is the span's pointer, we think the box also does not escape, and so stack allocate it. Later on a callee extracts the object from the span and passes it to a runtime helper, which blows up. [adding more details] The capture happens here: Root (boxes): Intermediary (captures box in span): runtime/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Lines 455 to 457 in 09c5809
|
Regex test failures seem to be from stack allocation here: Lines 1543 to 1546 in c332820
|
I suspect cc @EgorBo -- subtle unsafe issue? Hmm, maybe that's not it (though seems like we should pad arrays) -- we are stack allocating an array and then tail calling and passing the span-captured array as an arg. That's more likely the problem. |
Yeah, I don't see any issue in that code. But even if there were - it's all safe arrays so should've thrown a deterministic OutOfBounds Exception? Although, it's nice that stack-allocated array can potentially highlight unprotected out of bound access 🙂 |
src/coreclr/jit/layout.cpp
Outdated
@@ -778,6 +778,7 @@ ClassLayoutBuilder ClassLayoutBuilder::BuildArray(Compiler* compiler, CORINFO_CL | |||
|
|||
ClrSafeInt<unsigned> totalSize(elementSize); | |||
totalSize *= static_cast<unsigned>(length); | |||
totalSize.AlignUp(TARGET_POINTER_SIZE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AndyAyersMS speaking of alignment - do you skip Aling8 types on 32bit? like array of doubles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We handle those.
We don't model Align8 within the layout. It is a property of the symbol that has the layout. Though I suppose we could start having layouts with alignment demands...
It was this bit, though I suppose it's ok...? Lines 1594 to 1618 in c332820
|
Looks like the issue is that we embed the wrong method table in a stack allocated array where the array method table is a runtime lookup. The array is span captured so wasn't getting stack allocated before this PR. Why this only fails on arm32 is a good question (the failing method is tier0->fullopt, so it's not PGO related). Before helper expansion we have:
and this produces the following method table store:
|
Fix some issues with array stack allocation that are currently hard to observe: * use correct method table for shared array types * pad array layout out to a multiple of TARGET_POINTER_SIZE It's currently not easy to observe the type of a stack allocated array as accessing the type causes escape. But this becomes possible when supporting span capture or once we have a version of the covariant store check that can handle stack allocated arrays. Non-padded layouts may mean elements just off the end of an odd-length short or byte array won't be zeroed, which some unsafe uses may expect. Added some test cases.
Span<T>
captured arrays can be passed to callees without escaping.Implement a very simplistic "field sensitive" analysis for
Span<T>
where we pretend the span is simply its byref field.